package org.apache.osgimaker.analyse.algorithm;

import java.io.IOException;
import java.io.PrintWriter;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import org.apache.osgimaker.analyse.AnalyseUtil;
import org.apache.osgimaker.analyse.algorithm.graph.AtomicVertex;
import org.apache.osgimaker.analyse.algorithm.graph.StrongComponent;
import org.apache.osgimaker.analyse.algorithm.graph.StrongComponentAnalyser;
import org.apache.osgimaker.analyse.algorithm.renderer.AtomicVertexRenderer;
import org.apache.osgimaker.analyse.algorithm.renderer.PlainStrongComponentRenderer;
import org.apache.osgimaker.analyse.algorithm.renderer.StrongComponentRenderer;
import org.apache.osgimaker.analyse.algorithm.renderer.TemplateBasedClassRenderer;
import org.apache.osgimaker.analyse.algorithm.renderer.XMLClassRenderer;
import org.apache.osgimaker.analyse.algorithm.renderer.XMLStrongComponentRenderer;
import org.apache.osgimaker.analyse.algorithm.util.StringPattern;
import org.apache.osgimaker.analyse.algorithm.util.Text;
import org.apache.osgimaker.analyse.algorithm.util.TrueStringPattern;

/**
 * @author Tiger Gui
 * tigergui@gmail.com - Beijing China
 * 
 */
public class CodeAnalyser {
	private static final String VERSION = "1.4";
	private static final DateFormat DATE_FORMAT = new SimpleDateFormat(
			"yyyy-MM-dd");
	private static final String CSV_TEMPLATE = "{0},{1},{3},{2},{4},{5},{6},{7},{8},{9}\n";

	private final String[] _classFiles;
	private final StringPattern _pattern;
	private final StringPattern _reflectionPattern;
	private final boolean _mergeInnerClasses;
	private StrongComponentAnalyser _classAnalyser;
	private StrongComponentAnalyser _packageAnalyser;

	/**
	 * Creates an instance for the specified files or folders.
	 * 
	 * @param classFiles
	 *            Absolute or relative file names.
	 */
	public CodeAnalyser(String[] classFiles) {
		this(classFiles, new TrueStringPattern(), null, false);
	}

	/**
	 * Creates an instance for the specified files or folders which are filtered
	 * by the specified {@link StringPattern} object.
	 * 
	 * @param classFiles
	 *            Absolute or relative file names.
	 * @param pattern
	 *            Pattern fully-qualified class name have to match in order to
	 *            be a part of the class graph.
	 * @param reflectionPattern
	 *            Pattern ordinary string constants of a class file have to
	 *            fullfill in order to be handled as a class references. In
	 *            addition such strings have to be syntactically valid fully
	 *            qualified class names. If <tt>null</tt> ordinary string
	 *            constants will not be checked.
	 * @param mergeInnerClasses
	 *            If <code>true</code> merge inner classes with its outer class
	 */
	public CodeAnalyser(String[] classFiles, StringPattern pattern,
			StringPattern reflectionPattern, boolean mergeInnerClasses) {
		_classFiles = classFiles;
		_pattern = pattern;
		_reflectionPattern = reflectionPattern;
		_mergeInnerClasses = mergeInnerClasses;
	}

	/**
	 * Parses the class files and creates the class graph.
	 * 
	 * @return the duration of this operation in milliseconds.
	 * @throws IOException
	 *             if a problem occured during reading
	 */
	public long createClassGraph() throws IOException {
		long time = System.currentTimeMillis();
		AtomicVertex[] classGraph = Parser.readClassFiles(_classFiles,
				_pattern, _reflectionPattern, _mergeInnerClasses);
		_classAnalyser = new StrongComponentAnalyser(classGraph);
		return System.currentTimeMillis() - time;
	}

	/**
	 * Returns the class graph. Invokes {@link #createClassGraph()} if not
	 * already invoked.
	 */
	public AtomicVertex[] getClassGraph() {
		if (_classAnalyser == null) {
			try {
				createClassGraph();
			} catch (IOException e) {
				throw new RuntimeException(e.toString());
			}
		}
		return _classAnalyser.getGraph();
	}

	/**
	 * Counts the number of external classes.
	 */
	public int getNumberOfExternalClasses() {
		AtomicVertex[] graph = getClassGraph();
		HashSet usedClasses = new HashSet();
		int result = 0;
		for (int i = 0; i < graph.length; i++) {
			AtomicVertex vertex = graph[i];
			for (int j = 0, n = vertex.getNumberOfOutgoingArcs(); j < n; j++) {
				ClassAttributes attributes = (ClassAttributes) vertex
						.getHeadVertex(j).getAttributes();
				if (attributes.getType() == ClassAttributes.UNKNOWN) {
					if (!usedClasses.contains(attributes.getName())) {
						result++;
						usedClasses.add(attributes.getName());
					}
				}
			}
		}
		return result;
	}

	/**
	 * Condenses the class graph to an acyclic graph of its strong components.
	 * 
	 * @return the duration of this operation in milliseconds.
	 * @throws IllegalStateException
	 *             if this method is called before {@link #createClassGraph()}.
	 */
	public long condenseClassGraph() {
		checkClassGraph("condenseClassGraph()");
		long time = System.currentTimeMillis();
		_classAnalyser.getCondensedGraph();
		return System.currentTimeMillis() - time;
	}

	/**
	 * Returns the condensed the class graph, i.e.&nbsp;the acyclic graph of its
	 * strong components.
	 * 
	 * @throws IllegalStateException
	 *             if this method is called before {@link #createClassGraph()}.
	 */
	public StrongComponent[] getCondensedClassGraph() {
		checkClassGraph("getCondenseClassGraph()");
		return _classAnalyser.getCondensedGraph();
	}

	/**
	 * Calculates the for each class its layer index. The layer index of a class
	 * is the length of the longest path in the acyclic graph of strong
	 * components starting at the strong component to which the class belongs.
	 * 
	 * @return the duration of this operation in milliseconds.
	 * @throws IllegalStateException
	 *             if this method is called before {@link #createClassGraph()}.
	 */
	public long calculateClassLayerMap() {
		checkClassGraph("calculateClassLayerMap()");
		long time = System.currentTimeMillis();
		_classAnalyser.getLayerMap();
		return System.currentTimeMillis() - time;
	}

	/**
	 * Calculates the for each class its layer index and returns a <tt>Map</tt>
	 * where the classes are the keys (type {@link AtomicVertex}) and the layer
	 * indices are the values (type <tt>Integer</tt>).
	 * 
	 * @throws IllegalStateException
	 *             if this method is called before {@link #createClassGraph()}.
	 */
	public Map getClassLayerMap() {
		checkClassGraph("getClassLayerMap()");
		return _classAnalyser.getLayerMap();
	}

	/**
	 * Creates the package graph from the class graph.
	 * 
	 * @return the duration of this operation in milliseconds.
	 * @throws IllegalStateException
	 *             if this method is called before {@link #createClassGraph()}.
	 */
	public long createPackageGraph() {
		checkClassGraph("createPackageGraph()");
		long time = System.currentTimeMillis();
		PackageProcessor processor = new PackageProcessor();
		processor.deepSearchFirst(_classAnalyser.getGraph());
		_packageAnalyser = new StrongComponentAnalyser(processor.getGraph());
		return System.currentTimeMillis() - time;
	}

	/**
	 * Prints a CSV report into the specified writer. Delimiter is ','. First, a
	 * header with column titles is print. The columns are
	 * <ol>
	 * <li>class name
	 * <li>inner class (<tt>false</tt> or <tt>true</tt>)
	 * <li>size (in bytes)
	 * <li>used by (number of classes using this class)
	 * <li>uses internal classes (number of classes of the graph used by this
	 * class)
	 * <li>uses external classes (number of external classes used by this class)
	 * <li>layer index
	 * </ol>
	 * 
	 * @param writer
	 *            Output stream.
	 */
	public void printCSV(PrintWriter writer) {
		StrongComponent[] cycles = getCondensedClassGraph();
		AtomicVertex[] graph = getClassGraph();
		Map map = getClassLayerMap();
		writer.println("class name,type,inner class,size,used by,"
				+ "uses internal classes,uses external classes,"
				+ "layer index,cycle,source");
		render(graph, cycles, map,
				new TemplateBasedClassRenderer(CSV_TEMPLATE), writer);
		writer.close();
	}

	/**
	 * Prints for each class a raw output into the specified writer. This output
	 * includes all classes used by the class.
	 * 
	 * @param writer
	 *            Output stream.
	 */
	public void printRaw(PrintWriter writer) {
		AtomicVertex[] graph = getClassGraph();
		for (int i = 0; i < graph.length; i++) {
			AtomicVertex vertex = graph[i];
			writer.println(vertex.getAttributes());
			for (int j = 0, n = vertex.getNumberOfOutgoingArcs(); j < n; j++) {
				writer.println("    " + vertex.getHeadVertex(j).getAttributes());
			}
		}
		writer.close();
	}

	/**
	 * Prints for each strong component of the class graph a raw output into the
	 * specified writer. The strong component must have at least
	 * <tt>minSize</tt> classes in order to be printed out. This output includes
	 * all classes of the strong component.
	 * 
	 * @param writer
	 *            Output stream.
	 * @param minSize
	 *            Minimum size of the strong component.
	 * @throws IllegalStateException
	 *             if this method is called before {@link #createClassGraph()}.
	 */
	public void printComponents(PrintWriter writer, int minSize) {
		checkClassGraph("printComponents()");
		StrongComponent[] components = getCondensedClassGraph();
		StrongComponentRenderer renderer = new PlainStrongComponentRenderer();
		for (int i = 0; i < components.length; i++) {
			StrongComponent component = components[i];
			if (component.getNumberOfVertices() >= minSize) {
				writer.println(renderer.render(component));
			}
		}
		writer.close();
	}

	private void checkClassGraph(String method) {
		if (_classAnalyser == null) {
			throw new IllegalStateException(method
					+ " should be invoked after createClassGraph().");
		}
	}

	/**
	 * Returns the package graph created the class graph.
	 * 
	 * @throws IllegalStateException
	 *             if this method is called before {@link #createClassGraph()}.
	 */
	public AtomicVertex[] getPackageGraph() {
		if (_packageAnalyser == null) {
			createPackageGraph();
		}
		return _packageAnalyser.getGraph();
	}

	/**
	 * Condenses the package graph to an acyclic graph of its strong components.
	 * 
	 * @return the duration of this operation in milliseconds.
	 * @throws IllegalStateException
	 *             if this method is called before {@link #createPackageGraph()}
	 *             .
	 */
	public long condensePackageGraph() {
		checkPackageGraph("condensePackageGraph()");
		long time = System.currentTimeMillis();
		_packageAnalyser.getCondensedGraph();
		return System.currentTimeMillis() - time;
	}

	/**
	 * Returns the condensed package graph, i.e.&nbsp;the acyclic graph of its
	 * strong components.
	 * 
	 * @return the duration of this operation in milliseconds.
	 * @throws IllegalStateException
	 *             if this method is called before {@link #createPackageGraph()}
	 *             .
	 */
	public StrongComponent[] getCondensedPackageGraph() {
		checkPackageGraph("getCondensedPackageGraph()");
		return _packageAnalyser.getCondensedGraph();
	}

	/**
	 * Calculates the for each package its layer index. The layer index of a
	 * package is the length of the longest path in the acyclic graph of strong
	 * components starting at the strong component to which the package belongs.
	 * 
	 * @return the duration of this operation in milliseconds.
	 * @throws IllegalStateException
	 *             if this method is called before {@link #createPackageGraph()}
	 *             .
	 */
	public long calculatePackageLayerMap() {
		checkPackageGraph("calculatePackageLayerMap()");
		long time = System.currentTimeMillis();
		_packageAnalyser.getLayerMap();
		return System.currentTimeMillis() - time;
	}

	/**
	 * Calculates the for each package its layer index and returns a
	 * <tt>Map</tt> where the packages are the keys (type {@link AtomicVertex})
	 * and the layer indices are the values (type <tt>Integer</tt>).
	 * 
	 * @throws IllegalStateException
	 *             if this method is called before {@link #createPackageGraph()}
	 *             .
	 */
	public Map getPackageLayerMap() {
		checkPackageGraph("getPackageLayerMap()");
		return _packageAnalyser.getLayerMap();
	}

	/**
	 * Reads and analyses class files. Does only package analysis if
	 * <tt>packagesOnly == true</tt>. Reports progress of analysis on
	 * <tt>System.out</tt>.
	 * 
	 * @throws IOException
	 *             in case of reading problems.
	 */
	public void readAndAnalyse(boolean packagesOnly) throws IOException {
		System.out.print("analyse class files and create class graph ... ");
		long duration = createClassGraph();
		System.out.println("done after " + duration + " ms: "
				+ getClassGraph().length + " classes analysed.");

		if (!packagesOnly) {
			// Condense class graph
			System.out.print("condense class graph ... ");
			duration = condenseClassGraph();
			System.out.println("done after " + duration + " ms: "
					+ getCondensedClassGraph().length
					+ " strong components found.");

			// Calculate class layer
			System.out.print("calculate class layer indices ... ");
			duration = calculateClassLayerMap();
			System.out.println("done after " + duration + " ms.");
		}
		System.out.print("create package graph ... ");
		duration = createPackageGraph();
		System.out.println("done after " + duration + " ms: "
				+ getPackageGraph().length + " packages.");
		// Condense package graph
		System.out.print("condense package graph ... ");
		duration = condensePackageGraph();
		System.out.println("done after " + duration + " ms: "
				+ getCondensedPackageGraph().length
				+ " strong components found.");
		// Calculate package layer
		System.out.print("calculate package layer indices ... ");
		duration = calculatePackageLayerMap();
		System.out.println("done after " + duration + " ms.");
	}

	/**
	 * Prints an XML report into the specified writer.
	 * 
	 * @param title
	 *            Title of the report.
	 * @param packagesOnly
	 *            if <tt>true</tt> classes are omitted.
	 * @param writer
	 *            Output stream.
	 * @throws IllegalStateException
	 *             if this method is called before {@link #createPackageGraph()}
	 *             .
	 */
	public void printXML(String title, boolean packagesOnly, PrintWriter writer) {
		checkPackageGraph("printXML()");
		writer.println("<?xml version='1.0' encoding='UTF-8'?>");
		writer.print("<analyseresult title='");
		writer.print(Text.excapeForXML(title));
		writer.print("' date='");
		writer.print(DATE_FORMAT.format(new Date()));
		writer.println("'>");
		if (!packagesOnly) {
			StrongComponent[] components = getCondensedClassGraph();
			writer.println("  <classCycles>");
			StrongComponentRenderer sRenderer = new XMLStrongComponentRenderer(
					2);
			for (int i = 0; i < components.length; i++) {
				writer.print(sRenderer.render(components[i]));
			}
			writer.println("  </classCycles>");
			writer.println("  <classes numberOfExternalClasses=\""
					+ getNumberOfExternalClasses() + "\">");
			AtomicVertex[] graph = getClassGraph();
			Map layerMap = getClassLayerMap();
			render(graph, components, layerMap, new XMLClassRenderer(), writer);
			writer.println("  </classes>");
		}
		StrongComponent[] components = getCondensedPackageGraph();
		writer.println("  <packageCycles>");
		StrongComponentRenderer sRenderer = new XMLPackageStrongComponentRenderer(
				2);
		for (int i = 0; i < components.length; i++) {
			writer.print(sRenderer.render(components[i]));
		}
		writer.println("  </packageCycles>");
		writer.println("  <packages>");
		AtomicVertex[] graph = getPackageGraph();
		Map layerMap = getPackageLayerMap();
		render(graph, components, layerMap, new XMLPackageRenderer(), writer);
		writer.println("  </packages>");

		writer.println("</analyseresult>");
		writer.close();
	}
	
	private void render(AtomicVertex[] graph, StrongComponent[] cycles,
			Map layerMap, AtomicVertexRenderer renderer, PrintWriter writer) {
		List list = AnalyseUtil.getTrueCycles(cycles);
		for (int i = 0; i < graph.length; i++) {
			AtomicVertex vertex = graph[i];
			Integer layerIndex = (Integer) layerMap.get(vertex);
			writer.print(renderer.render(vertex, AnalyseUtil.getCycleFor(vertex, list),
					layerIndex == null ? -1 : layerIndex.intValue()));
		}
	}

	private void checkPackageGraph(String method) {
		if (_packageAnalyser == null) {
			throw new IllegalStateException(method
					+ " should be invoked after createPackageGraph().");
		}
	}

}
